import { App, getLinkpath, Notice, parseLinktext, ReferenceCache, TFile, FileSystemAdapter, Editor } from 'obsidian';
import { path } from './path';
import { Utils } from './utils';
import { AbitUsefulSettings } from './main';
import { ConfirmModalOptions, ConfirmModal } from 'ConfirmModal';

// import * as path from 'path';

export class LinksHandler {
	addTwoSpaceBeforeEnter(editor: Editor) {
		let selection = editor.getSelection();
		selection = selection.replace(/\x20*\n/g, '\x20\x20\n');
		editor.replaceSelection(selection);
	}

	settings: AbitUsefulSettings;
	app: App;
	constructor(app: App, settings: AbitUsefulSettings) {
		this.settings = settings;
		this.app = app;
	}



	/**
	 * 导出（移动）一个笔记及链接文件到指定的导出目录
	 * @param {TFile} note 
	 * @returns 
	 */

	async exportNote(note: TFile) {
		if (note.extension != "md") {
			new Notice("请指定一个markdown笔记文件名！", 10);
			return
		}
		const settedAction = this.settings.deleteOriginNote ? 'move' : 'copy';
		const exportFolder = this.settings.exportNotesFolder;
		interface Note {
			note: TFile;
			upNote: TFile | undefined;
			active: "copy" | "move";
			targetPath: string | undefined;
			attachmentRecords: Attachement[];
		}
		interface Attachement {
			referenceCache: ReferenceCache;
			upNote: TFile;
			attachment: TFile | null;
			action: "copy" | "move";
			targetPath: string;
			// upFolder: string;

		}
		const modalOptions: ConfirmModalOptions = {
			title: '导出笔记',
			info: '将所选笔记导出到一个导出目录中，包括所有此笔记递归链接的笔记及附件，所链接的笔记及附件将递归放入 \
				以笔记命名的子文件夹中。请仔细检查你的插件选项配置，如果笔记及附件没有多个入链且配置为移动附件，  \
				笔记的所有附件将与笔记一同移动，否则为复制。',
			setInfo: '请小心操作,确认是否继续？',
			buttons: [{
				btnText: '继续',
				btnAction: async () => {
					await processNote(note);
				}
			}, { btnText: '取消', }]
		}
		const modal = new ConfirmModal(this.app, modalOptions);
		modal.open();
		const processNote = async (note: TFile, upNote?: TFile, targetPath?: string) => {
			const noteRecord: Note = {
				note: note,
				upNote: upNote,
				active: 'copy',
				targetPath: targetPath,
				attachmentRecords: [],
			}

			// 提取笔记中所有链接,
			const metadataCache = this.app.metadataCache.getFileCache(note);
			const linkCaches = metadataCache?.links;
			const embedCaches = metadataCache?.embeds;
			const referenceCaches: ReferenceCache[] = [...linkCaches ?? [], ...embedCaches ?? []];

			const saveFolder = exportFolder;


			// 获取入链笔记
			const inlinks = this.getInlinkNotes(note);
			// let hasInlinks = true;
			let canMove = false;
			//如果入链笔记为0或者为自身则表明当前笔记不被其它笔记链接
			if (inlinks.notes.length === 0 || (inlinks.notes.length == 1 && inlinks.notes[0] === noteRecord.note.path)) {
				// hasInlinks = false;
				canMove = true;
			}
			// 笔记为附件，入链笔记只有1个且为主笔记
			if (noteRecord.upNote && inlinks.notes.length == 1 && inlinks.notes[0] === noteRecord.upNote.path) {
				canMove = true;
			}
			if (noteRecord.upNote && inlinks.notes.length == 2) {
				if (inlinks.notes[0] == noteRecord.upNote.path
					&& inlinks.notes[1] == note.path ||
					inlinks.notes[0] == note.path && inlinks.notes[1] == noteRecord.upNote.path)
					canMove = true;
			}

			if (canMove && settedAction == 'move') { noteRecord.active = 'move' }
			else { noteRecord.active = 'copy' }

			//note为开始的笔记，不是被链接的
			if (!noteRecord.targetPath) {
				noteRecord.targetPath = path.join(saveFolder, note.name);
			}/*  else {
				saveFolder = path.join(path.dirname(targetPath), note.basename);
				noteRecord.targetPath = path.join(saveFolder, note.name);
			} */
			const extname = path.extname(noteRecord.targetPath);
			const basename = path.basename(noteRecord.targetPath, extname);
			const dirname = path.dirname(noteRecord.targetPath);
			while (this.app.vault.getFileByPath(noteRecord.targetPath)) {
				noteRecord.targetPath = path.join(dirname, basename + '-' +
					Math.random().toString(36).substring(7, 14) + extname);
			}
			if (!this.app.vault.getFolderByPath(path.dirname(noteRecord.targetPath)))
				await this.app.vault.createFolder(path.dirname(noteRecord.targetPath));

			referenceCaches.forEach((referenceCache) => {
				const linkPath = getLinkpath(referenceCache.link);
				const attachment = this.app.metadataCache.getFirstLinkpathDest(linkPath, noteRecord.note.path);
				const extname = noteRecord.targetPath && path.extname(noteRecord.targetPath);
				noteRecord.attachmentRecords.push({
					referenceCache,
					upNote: noteRecord.note,
					attachment: attachment,
					action: noteRecord.active,
					targetPath: (attachment
						&& noteRecord.targetPath
						&& path.join(path.dirname(noteRecord.targetPath), path.basename(noteRecord.targetPath, extname), attachment.name)) ?? '',
				})

			});


			await Promise.allSettled(noteRecord.attachmentRecords.map(async (attachmentRecord) => {
				await processAttachment(attachmentRecord);
			}));



			if (noteRecord.active === 'copy') {
				await this.app.vault.copy(noteRecord.note, noteRecord.targetPath)
			}
			if (noteRecord.active === 'move') {
				await this.app.vault.rename(noteRecord.note, noteRecord.targetPath)
			}

			/*  update link */
			// 根据偏移降序
			noteRecord.attachmentRecords.sort((a, b) => b.referenceCache.position.start.offset - a.referenceCache.position.start.offset);
			const targetNote = noteRecord.targetPath && this.app.vault.getFileByPath(noteRecord.targetPath);
			let targetNoteContent = targetNote && await this.app.vault.read(targetNote);
			for (const attachmentRecord of noteRecord.attachmentRecords) {
				const { referenceCache, targetPath } = attachmentRecord;
				if (noteRecord.targetPath) {
					// 此行产生绝对路径的link
					// const linkPath = this.app.metadataCache.fileToLinktext(attachment, noteRecord.targetPath);

					// 产生相对路径的link
					let linkPath = targetPath === noteRecord.targetPath ?
						'./' + path.basename(targetPath)
						: './' + path.join(path.basename(path.dirname(targetPath)), path.basename(targetPath));

					//空格问题
					linkPath = Utils.normalizePathForLink(linkPath);
					const originText = referenceCache.original;
					const parselink = parseLinktext(referenceCache.link);
					let displayText;
					if (parselink.path) {
						displayText = referenceCache.displayText ?? linkPath + parselink.subpath;
					}
					else {
						displayText = linkPath + referenceCache.displayText ?? parselink.subpath;
					}
					let mdLink = `[${displayText}](${linkPath}${parselink.subpath})`;
					if (originText.trimStart().startsWith('!')) {
						mdLink = '!' + mdLink;
					}
					const start = referenceCache.position.start.offset;
					const end = referenceCache.position.end.offset;
					targetNoteContent = targetNoteContent?.substring(0, start) + mdLink + targetNoteContent?.substring(end);
				}

			}
			targetNote && await this.app.vault.modify(targetNote, targetNoteContent??'');

		};

		const processAttachment = async (attachmentRecord: Attachement) => {

			const { attachment, upNote, } = attachmentRecord;
			let { action, targetPath } = attachmentRecord;

			let canMove = false;

			const extname = path.extname(targetPath);
			const basename = path.basename(targetPath, extname);
			const dirname = path.dirname(targetPath);
			while (this.app.vault.getFileByPath(targetPath)) {
				targetPath = path.join(dirname, basename
					+ '-' + Math.random().toString(36).substring(7, 14) + extname);
			}
			if (attachment) {
				// 防止回调自身死循环
				if (attachment === upNote) {
					attachmentRecord.targetPath = path.join(path.dirname(path.dirname(attachmentRecord.targetPath)), path.basename(attachmentRecord.targetPath));
					return;
				}
				if (attachment.extension === 'md') { await processNote(attachment, upNote, targetPath) }
				else {
					const inlinkNotes = this.getInlinkNotes(attachment);
					if (inlinkNotes.notes.length == 1 && inlinkNotes.notes[0] === upNote.path) {
						canMove = true;
					}
					if (canMove && action === 'move') { action = 'move' }
					else { action = 'copy' }
					const saveFolder = path.dirname(targetPath);
					if (!this.app.vault.getFolderByPath(saveFolder)) await this.app.vault.createFolder(saveFolder);
					let finishied = false;
					do

						try {
							if (action === 'copy') {
								await this.app.vault.copy(attachment, targetPath);
							}
							if (action === 'move') {
								await this.app.vault.rename(attachment, targetPath);
							}

							finishied = true;
						} catch (error) {
							while (this.app.vault.getFileByPath(targetPath)) {
								targetPath = path.join(dirname, basename
									+ '-' + Math.random().toString(36).substring(7, 14) + extname);
							}
						}
					while (!finishied);
					attachmentRecord.targetPath = targetPath;
				}
			}
		}


	}








	/**检查所有笔记的附件是否存放在预设的附件目录中 */
	async checkAllNoteAttachments() {
		// const notes = this.app.vault.getMarkdownFiles();
		const filePath = this.settings.allNoteAttachmentResultFile;
		let resultFile = this.app.vault.getFileByPath(filePath);
		if (resultFile) await this.app.vault.delete(resultFile);
		resultFile = await this.app.vault.create(filePath, '');
		const allDiff = (await Promise.all(Object.entries(this.app.metadataCache.resolvedLinks)
			.map(async (noteRecord) => {
				const note = noteRecord[0];
				if (note === filePath) return null;
				const attachmentRecords = noteRecord[1];
				return await this.getAttachRecordDiffAttachments(attachmentRecords, note);
			}))).filter(value => value);
		const allDiffText = allDiff.map((diff, index) => {
			const notePath = diff?.note;
			const resultTitle = `\n\n${index}. [[${notePath}]]`;
			const resultContent = diff && diff.diffAttachments.map((diffAttachment) => `\n+:${diffAttachment?.attachment}  \n-:${diffAttachment?.expected}  `)
				.join('\n');
			return resultTitle + resultContent;
		}).join('\n');
		await this.app.vault.append(resultFile, allDiffText);
	}


	/** 返回笔记中不符合系统预设的附件
	 * @param {note} 笔记文件或路径
	 * @returns  返回不符合预设的附件 {
	 * note:filePath,diffAttachments:{attachement, expected}
	 *      note 笔记
	 *      diffAttachments 不符合的附件数组
	 * 		attachment 附件
	 * 		expecte  预设附件}
	 */

	private async getNoteDiffAttachments(note: TFile | string, includeMd = false) {
		const notePath: string = note instanceof TFile ? note.path : note;
		const attachmentRecord = this.app.metadataCache.resolvedLinks[notePath];
		return await this.getAttachRecordDiffAttachments(attachmentRecord, notePath, includeMd);
	}

	private async getAttachRecordDiffAttachments(attachmentRecord: Record<string, number>, notePath: string, includeMd = false)
		: Promise<{ note: string; diffAttachments: ({ attachment: string; expected: string; } | undefined)[]; } | undefined> {

		const diffAttachments =
			(await Promise.all(Object.entries(attachmentRecord)
				.map(
					async ([attachement]) => {
						const expectedPath = await this.app.fileManager.getAvailablePathForAttachment(attachement, notePath);
						if (includeMd) {
							if (path.dirname(expectedPath) !== path.dirname(attachement)) {
								return { attachment: attachement, expected: expectedPath };
							}
						} else {
							if (path.dirname(expectedPath) !== path.dirname(attachement) && path.extname(attachement) !== '.md') {
								return { attachment: attachement, expected: expectedPath };
							}
						}


					})
			)).filter(value => value);
		//如果没有不同附件，diffAttachments为[]
		return diffAttachments && diffAttachments.length !== 0 ? { note: notePath, diffAttachments } : undefined;
	}

	private displayDiffText(diff: { note: string; diffAttachments: ({ attachment: string; expected: string; } | undefined)[]; }) {

		const content = diff.diffAttachments.map((diffAttachments) => {
			return diffAttachments ? `<li><p>+附件位置:${diffAttachments.attachment}</p><p>-预设位置:${diffAttachments.expected}</p></li>` : '';
		}).join('\n');

		return `<h3>笔记：${diff.note}</h3><div><p>有以下附件与预设位置不同</p><ol>${content}</ol></div>`;

	}
	async collectNote(note: TFile) {
		const confirmModaloptions: ConfirmModalOptions = {
			title: '整理附件',
			info: '整理选定笔记的附件',
			setInfo: '请小心操作,确认是否继续？',
			buttons: [{
				btnText: '继续',
				btnAction: async () => {
					const diff = await this.getNoteDiffAttachments(note);
					if (diff) {
						const options: ConfirmModalOptions = {
							elementHTML: this.displayDiffText(diff),
							setInfo: '请小心操作,确认是否继续？',
							buttons: [{
								btnText: '继续',
								btnAction: async () => {
									this.collectAttachment(note);
								}
							}, { btnText: '取消', }]
						};
						const modal = new ConfirmModal(this.app, options);
						modal.open();
					}
				}
			}, { btnText: '取消', }]

		}
		const confirmModal = new ConfirmModal(this.app, confirmModaloptions);
		confirmModal.open();


	}
	async collectAttachment(note: TFile) {
		const diff = await this.getNoteDiffAttachments(note);
		diff?.diffAttachments.forEach((diffAttachment) => {
			if (diffAttachment) {
				const inLinkNotes = this.getInlinkNotes(diffAttachment.attachment);
				// 对于有多个笔记链接到的附件不进行整理
				if (inLinkNotes.notes.length !== 1)
					this.displayInlinkNotes(inLinkNotes);
				else {
					if (inLinkNotes.notes[0] === note.path) {
						const file = this.app.vault.getFileByPath(diffAttachment.attachment);
						file && this.app.fileManager.renameFile(file, diffAttachment.expected);
					}
				}
			}

		})

	}

	/**返回文件反向链入的文件，返回object结构 */
	getInlinkNotes(attachment: TFile | string) {
		const attachmentPath = typeof attachment === 'string' ? attachment : attachment.path;
		const resolved = this.app.metadataCache.resolvedLinks;
		const notes = Object.entries(resolved)
			.filter((noteRecord) => Object.entries(noteRecord[1]).some((attachmentRecord) => attachmentPath === attachmentRecord[0]))
			.map((noteRecord) => noteRecord[0]);
		return { attachment: attachmentPath, notes: notes };
	}
	displayInlinkNotes(inLinkNotes: { attachment: string, notes: string[] }) {
		const noticeBottom = `<h1>多个笔记链接到同一个附件:</h1><p>${inLinkNotes.attachment}</p>`;
		let noticeText = inLinkNotes.notes.map(notePath => `<p>${notePath}</p>`).join('');
		noticeText = noticeText + noticeBottom;
		const options: ConfirmModalOptions = {
			setInfo: '附件存在多个入链笔记，附件将不被移动或复制',
			elementHTML: noticeText,
			buttons: [{
				btnText: '确定',
			}]
		}
		const modal = new ConfirmModal(this.app, options);
		modal.open();
	}
	async checkAllFileInLinkNotes() {
		const allFiles = this.app.vault.getFiles();
		const filePath = this.settings.allInlinkNotesResultFile;
		let resultFile = this.app.vault.getFileByPath(filePath);
		if (resultFile) await this.app.vault.delete(resultFile);
		resultFile = await this.app.vault.create(filePath, '');
		//将有反向链入的文件写入结果文件
		const result = allFiles.map((file => {
			const inLinkNotes = this.getInlinkNotes(file);
			if (inLinkNotes.notes.length > 0) {
				return `### ${inLinkNotes.attachment}  \n` + inLinkNotes.notes.map(filePath => `- [[${filePath}]]  \n`).join('');
			}
		})).filter(vault => vault).join('');
		resultFile && await this.app.vault.append(resultFile, result);
	}

	async showInLinkNotes(file: TFile) {
		const inLinkNotes = this.getInlinkNotes(file);
		let showInfo = '';
		if (inLinkNotes.notes.length > 0) {
			const adapter = this.app.vault.adapter;
			const resultText = inLinkNotes.notes.map(filePath => `<p><a src=${(adapter as FileSystemAdapter).getFilePath(filePath)}>${filePath}</a></p>`).join('');
			showInfo = resultText;
		} else {
			showInfo = '无链入笔记';
		}

		const options: ConfirmModalOptions = {
			title: '链入的笔记',
			elementHTML: showInfo,
			buttons: [{ btnText: '确定' }],
		}
		const modal = new ConfirmModal(this.app, options);
		modal.open();
		return modal.finised

	}

	async showNoteStatus(file: TFile) {
		//显示入链笔记
		await this.showInLinkNotes(file);
		await this.showAllDiff(file);



	}

	async showAllDiff(note: TFile) {
		const diff = await this.getNoteDiffAttachments(note, true);
		const diffAttachmentsText = diff && this.displayDiffText(diff);
		if (diffAttachmentsText) {
			const options: ConfirmModalOptions = {
				info: '',
				elementHTML: diffAttachmentsText,
				setInfo: '笔记附件存储位置与系统设置不符情况',
				buttons: [{ btnText: '确定' }],
			};
			const modal = new ConfirmModal(this.app, options);
			modal.open();
			await modal.finised;
		}
		diff && diff.diffAttachments.forEach(async (attachmentRecord) => {
			if (attachmentRecord) {
				const outLinkFile = this.app.vault.getFileByPath(attachmentRecord.attachment);
				if (outLinkFile && outLinkFile != note && path.extname(attachmentRecord.attachment) === '.md')
					await this.showAllDiff(outLinkFile);
			}
		})
	}
}
